﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Collections;
using System.Data.SqlTypes;
using System.Drawing;

namespace Noris.Tools.TraceVisualiser.Support
{
	/// <summary>
	/// Třída, která obsahuje extenze .NET tříd = rozšíření základní funkcionality
	/// </summary>
	public static class NetExtensions
	{
		#region Object
        /// <summary>
        /// Vrátí ToString() objektu this, ale pokud this je null, pak vrátí text "null".
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string ToStringNull(this object value)
        {
            if (value == null) return "null";
            return value.ToString();
        }
        #endregion
		#region String
		/// <summary>
		/// Vrací daný string otrimovaný, anebo pokud ne na vstupu null, pak vrací Empty.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static string AllTrim(this string value)
		{
			if (String.IsNullOrEmpty(value)) return String.Empty;
			return value.Trim();
		}
		/// <summary>
		/// Vrací true, pokud je daný string null nebo prázdný (mezery a TAB jsou prázdné, EOL není prázdný)
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public static bool IsEmpty(this string value)
		{
			if (String.IsNullOrEmpty(value)) return true;
			return (value.Trim(' ', '\t').Length == 0);
		}
        /// <summary>
        /// Rozdělí dodaný text na řádky. Volitelně odebere prázdné řádky, volitelně řádky trimuje.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static List<string> ToLines(this string value)
        {
            return _StringToLines(value, false, false);
        }
        /// <summary>
        /// Rozdělí dodaný text na řádky. Volitelně odebere prázdné řádky, volitelně řádky trimuje.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static List<string> ToLines(this string value, bool removeEmptyLines)
        {
            return _StringToLines(value, removeEmptyLines, false);
        }
        /// <summary>
        /// Rozdělí dodaný text na řádky. Volitelně odebere prázdné řádky, volitelně řádky trimuje.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static List<string> ToLines(this string value, bool removeEmptyLines, bool trimLines)
        {
            return _StringToLines(value, removeEmptyLines, trimLines);
        }
        /// <summary>
        /// Rozdělí dodaný text na řádky. Volitelně odebere prázdné řádky, volitelně řádky trimuje.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static List<string> _StringToLines(string value, bool removeEmptyLines, bool trimLines)
        {
            if (value == null) return new List<string>();

            string[] lines = value.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            List<string> result = new List<string>();
            foreach (string l in lines)
            {
                string line = (trimLines ? l.Trim() : l);
                if (!removeEmptyLines || (removeEmptyLines && !line.IsEmpty()))
                    result.Add(line);
            }
            return result;
        }
        /// <summary>
		/// Z this stringu odebere všechny výskyty znaků, předaných v parametru
		/// </summary>
		/// <param name="value"></param>
		/// <param name="removeChars"></param>
		/// <returns></returns>
		public static string RemoveChars(this string value, string removeChars)
		{
			string result = value;
			if (result != null && removeChars != null)
			{
				foreach (char r in removeChars)
				{
					if (result.Contains(r))
						result = result.Replace(r.ToString(), "");
				}
			}
			return result;
		}
		#endregion
        #region Type
        /// <summary>
        /// Vrátí true, pokud daný typ (this) implementuje alespoň jeden interface z předaných.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="implementInterface"></param>
        /// <returns></returns>
        public static bool ImplementInterface(this Type type, params Type[] implementInterface)
        {
            // Vstupy do hashtable:
            Hashtable ht = _GetTypeHashtable(implementInterface);

            // Přehled konkrétních interfaces:
            Type[] implfs = type.GetInterfaces();
            foreach (Type impl in implfs)
            {
                if (ht.ContainsKey(impl))
                    return true;
            }

            return false;
        }
        /// <summary>
        /// Vrátí true, pokud daný typ (this) je potomkem některého z daných typů (kteréhokoli).
        /// </summary>
        /// <param name="type"></param>
        /// <param name="predecessors"></param>
        /// <returns></returns>
        public static bool IsDescendant(this Type type, params Type[] predecessors)
        {
            // Vstupy do hashtable:
            Hashtable ht = _GetTypeHashtable(predecessors);

            // Prohledám předky:
            Type t = type;
            while (t != null)
            {
                if (ht.ContainsKey(t))
                    return true;

                t = t.BaseType;
            }
            return false;
        }
        /// <summary>
        /// Vrátí Namespace.Name
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetNamespaceName(this Type type)
        {
            if (type == null) return "";
            return type.Namespace + "." + type.Name;
        }
        /// <summary>
        /// Vrátí hashtable obsahující všechny dané typy
        /// </summary>
        /// <param name="types"></param>
        /// <returns></returns>
        private static Hashtable _GetTypeHashtable(Type[] types)
        {
            Hashtable ht = new Hashtable();
            foreach (Type type in types)
            {
                if (!ht.ContainsKey(type))
                    ht.Add(type, null);
            }
            return ht;
        }
        #endregion
        #region IEnumerable
        /// <summary>
        /// Vrátí daný soupis prvků převedený na string (item.ToString()), oddělený uvnitř čárkami.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static string ToStringList(this IEnumerable list)
        {
            return list.ToStringList(",");
        }
        /// <summary>
        /// Vrátí daný soupis prvků převedený na string (item.ToString()), oddělený uvnitř čárkami.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static string ToStringList(this IEnumerable list, string delimiter)
        {
            if (list == null) return null;

            bool first = true;
            StringBuilder sb = new StringBuilder();
            foreach (object item in list)
            {
                string text = (item == null ? "null" : item.ToString());
                sb.Append(text + (first ? "" : delimiter));
                if (first)
                    first = false;
            }
            return sb.ToString();
        }
        /// <summary>
        /// Vrátí průměrnou hodnotu dat, s vyloučením krajních hodnot.
        /// Za krajní hodnoty bere dolní a horní 1/6 hodnot.
        /// Průměr počítá tedy jen ze středních 2/3 hodnot.
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static decimal AverageQ<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector)
        {
            return source.AverageQ(selector, 6);
        }
        /// <summary>
        /// Vrátí průměrnou hodnotu dat, s vyloučením krajních hodnot.
        /// Za krajní hodnoty bere dolní a horní 1/N hodnot. N = parametr discard, musí být 6 a více.
        /// Průměr počítá tedy jen ze středních 4/N hodnot.
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <param name="discard"></param>
        /// <returns></returns>
        public static decimal AverageQ<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector, int discard)
        {
            List<decimal> list = source.Select(selector).ToList();
            int count = list.Count;
            int disc = (discard <= 6 ? 6 : discard);
            if (count >= discard)
            {   // Pokud mám 6 a více prvků, pak odeberu první a poslední 1/6, zbyde prostřední 2/3:
                list.Sort();
                int remove = (int)(Math.Round((decimal)count / (decimal)disc, 0));
                list.RemoveRange(count - remove, remove);
                list.RemoveRange(0, remove);
            }
            return list.Average();
        }
        #endregion
		#region Rectangle
		/// <summary>
		/// Metoda vrátí konkrétní bod v tomto Rectangle, který odpovídá danému zarovnání obsahu.
		/// </summary>
		/// <param name="area"></param>
		/// <param name="alignment"></param>
		/// <returns></returns>
		public static Point PivotPoint(this Rectangle area, ContentAlignment alignment)
		{
			int x = area.X;
			int y = area.Y;
			
			// Určit X:
			if (alignment == ContentAlignment.TopLeft || alignment == ContentAlignment.MiddleLeft || alignment == ContentAlignment.BottomLeft)
				// Left:
				x = area.X;
			else if (alignment == ContentAlignment.TopCenter || alignment == ContentAlignment.MiddleCenter || alignment == ContentAlignment.BottomCenter)
				// Center:
				x = area.X + area.Width  / 2;
			else if (alignment == ContentAlignment.TopRight || alignment == ContentAlignment.MiddleRight || alignment == ContentAlignment.BottomRight)
				x = area.Right;

			// Určit Y:
			if (alignment == ContentAlignment.TopLeft || alignment == ContentAlignment.TopCenter || alignment == ContentAlignment.TopRight)
				y = area.Y;
			else if (alignment == ContentAlignment.MiddleLeft || alignment == ContentAlignment.MiddleCenter || alignment == ContentAlignment.MiddleRight)
				y = area.Y + area.Height / 2;
			else if (alignment == ContentAlignment.BottomLeft || alignment == ContentAlignment.BottomCenter || alignment == ContentAlignment.BottomRight)
				y = area.Bottom;

			return new Point(x, y);

		}
		#endregion
        #region Sql typy a Nullable typy
        /// <summary>
        /// Vrátí Int32? hodnotu.
        /// Pokud na vstupu je SqlInt32.Null, vrací (null).
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static Int32? ToIntN(this SqlInt32 value)
        {
            if (value.IsNull) return (Int32?)null;
            return (Int32?)value.Value;
        }
        /// <summary>
        /// Vrátí SqlInt32 hodnotu.
        /// Pokud na vstupu je (null), vrací SqlInt32.Null.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static SqlInt32 ToSqlInt(this Int32? value)
        {
            if (!value.HasValue) return SqlInt32.Null;
            return new SqlInt32(value.Value);
        }
        #endregion
        #region LinkedList<T>
        /// <summary>
		/// Metoda najde v linkovaném seznamu první záznam, který odpovídá dané podmínce (predikát).
		/// Metoda vrací objekt na odpovídající pozici (typ LinkedListNode, ne jeho vnitřní Value).
		/// Pokud nenajde, vrátí null.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="linkedList">Seznam, pro který se vyhodnocení provádí</param>
		/// <param name="predicate">Předpoklad, který musí platit pro vrácený prvek. Delegát (např. anonymní), lambda výraz.</param>
		/// <returns></returns>
		public static LinkedListNode<T> FindByPredicate<T>(this LinkedList<T> linkedList, Predicate<T> predicate)
		{
			LinkedListNode<T> result = null;
			if (linkedList != null)
			{
				LinkedListNode<T> scan = linkedList.First;  // Počínaje prvním prvkem v seznamu
				while (scan != null)                        // Dokud jsou prvky
				{
					if (predicate(scan.Value))              // Pokud pro Value z tohoto prvku platí předpoklad, 
					{
						result = scan;                      //  pak tento prvek bude výsledkem metody.
						break;
					}
					scan = scan.Next;                       // Předpoklad neplatí, podívám se na další prvek.
				}
			}
			return result;
		}
		#endregion
        #region SqlDateType
        /// <summary>
        /// Vrátí true, pokud jsou dvě hodnoty shodné.
        /// Shodné jsou pokud jsou obě null, anebo pokud nejsou null a shoduje se jejich hodnota.
        /// </summary>
        /// <param name="curr"></param>
        /// <param name="other"></param>
        /// <returns></returns>
        public static bool IsEqual(this SqlInt32 curr, SqlInt32 other)
        {
            if (curr.IsNull && other.IsNull) return true;
            if (curr.IsNull && !other.IsNull) return false;
            if (!curr.IsNull && other.IsNull) return false;
            return (curr.Value == other.Value);
        }
        /// <summary>
        /// Vrátí true, pokud jsou dva datumy shodné.
        /// Shodné jsou pokud jsou oba null, anebo pokud nejsou null a shoduje se jejich datum a čas. Milisekundy se netestují. Sekundy se testují na požádání, defaultně ne.
        /// </summary>
        /// <param name="curr"></param>
        /// <param name="other"></param>
        /// <returns></returns>
        public static bool IsEqual(this SqlDateTime curr, SqlDateTime other)
        {
            if (curr.IsNull && other.IsNull) return true;
            if (curr.IsNull && !other.IsNull) return false;
            if (!curr.IsNull && other.IsNull) return false;
            DateTime c = curr.Value;
            DateTime o = other.Value;
            return (c.Date == o.Date && c.Hour == o.Hour && c.Minute == o.Minute);
        }
        /// <summary>
        /// Vrátí true, pokud jsou dva datumy shodné.
        /// Shodné jsou pokud jsou oba null, anebo pokud nejsou null a shoduje se jejich datum a čas. Milisekundy se netestují. Sekundy se testují na požádání, defaultně ne.
        /// </summary>
        /// <param name="curr"></param>
        /// <param name="other"></param>
        /// <param name="withSeconds">Porovnávat i sekundy (true = porovnat / false = ignorovat). Default je false.</param>
        /// <returns></returns>
        public static bool IsEqual(this SqlDateTime curr, SqlDateTime other, bool withSeconds)
        {
            if (curr.IsNull && other.IsNull) return true;
            if (curr.IsNull && !other.IsNull) return false;
            if (!curr.IsNull && other.IsNull) return false;
            DateTime c = curr.Value;
            DateTime o = other.Value;
            return (c.Date == o.Date && c.Hour == o.Hour && c.Minute == o.Minute && (!withSeconds || (withSeconds && c.Second == o.Second)));
        }
        #endregion
        #region XElement
        public static void AddAttribute(this XElement element, string attributeName, string value)
        {
            element.Add(new XAttribute(attributeName, (value == null ? "" : value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, int value)
        {
            element.Add(new XAttribute(attributeName, Convertor.Int32ToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, Int32? value)
        {
            element.Add(new XAttribute(attributeName, Convertor.Int32NToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, decimal value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DecimalToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, bool value)
        {
            element.Add(new XAttribute(attributeName, Convertor.BooleanToString(value, "1", "0")));
        }
        public static void AddAttribute(this XElement element, string attributeName, DateTime value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DateTimeToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, TimeRange value)
        {
            element.Add(new XAttribute(attributeName, Convertor.TimeRangeToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, Rectangle value)
        {
            element.Add(new XAttribute(attributeName, Convertor.RectangleToString(value)));
        }
        public static string GetAttributeString(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return null;
            return attr.Value;
        }
        public static Int32 GetAttributeInt32(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0;
            return (int)Convertor.StringToInt32(attr.Value);
        }
        public static Int32? GetAttributeInt32N(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return (Int32?)null;
            return (Int32?)Convertor.StringToInt32N(attr.Value);
        }
        public static decimal GetAttributeDecimal(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0m;
            return (decimal)Convertor.StringToDecimal(attr.Value);
        }
        public static bool GetAttributeBoolean(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return false;
            return (bool)Convertor.StringToBoolean(attr.Value);
        }
        public static DateTime GetAttributeDateTime(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return DateTime.MinValue;
            return (DateTime)Convertor.StringToDateTime(attr.Value);
        }
        public static TimeRange GetAttributeTimeRange(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return TimeRange.Empty;
            return (TimeRange)Convertor.StringToTimeRange(attr.Value);
        }
        public static Rectangle GetAttributeRectangle(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return Rectangle.Empty;
            return (Rectangle)Convertor.StringToRectangle(attr.Value);
        }

        private static XAttribute GetXAttribute(XElement element, string attributeName)
        {
            if (element == null) return null;
            XAttribute attr = element.Attribute(attributeName);
            return attr;
        }
        public static void Save(this XDocument xDoc, string file, bool textFormat)
        {
            System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
            settings.ConformanceLevel = System.Xml.ConformanceLevel.Document;
            settings.Encoding = Encoding.UTF8;
            settings.Indent = textFormat;
            settings.IndentChars = "  ";
            settings.NewLineHandling = System.Xml.NewLineHandling.Entitize;
            settings.NewLineChars = Environment.NewLine;
            settings.NewLineOnAttributes = false;
            settings.OmitXmlDeclaration = false;
            using (System.Xml.XmlWriter writter = System.Xml.XmlWriter.Create(file, settings))
            {
                xDoc.Save(writter);
            }
        }
        #endregion
    }
	#region DELEGÁT FindItem
	/// <summary>
	/// Předpis pro delegáta, který najde a vrátí určitý objekt (generického typu T) pro klíč int
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="key"></param>
	/// <returns></returns>
	public delegate T FindItem<T>(int key) where T : class;
	#endregion
    #region Class, method, property atributy
    /// <summary>
    /// Atribut, který dovoluje (pro daný enum na kterém je použit) určit název editačního stylu Helios Green, s nímž je enum spřízněn.
    /// Využije se při vyhledání DisplayValue pro hodnotu enumu (v metodě Noris.Schedule.Support.Green.Repository.EditStyles.GetDispVal(value)).
    /// Spřízněný editační styl může mít ValueType = Text nebo Integer, podle toho se pak určí hodnota enumu (char nebo int).
    /// </summary>
    [AttributeUsage(AttributeTargets.Enum, Inherited = false, AllowMultiple = true)]
    public sealed class EditStyleNameAttribute : Attribute
    {
        /// <summary>
        /// Určí spřízněný editační styl
        /// </summary>
        /// <param name="editStyleName">Název spřízněného editačního stylu</param>
        public EditStyleNameAttribute(string editStyleName)
        {
            this._EditStyleName = editStyleName;
        }
        readonly string _EditStyleName;
        /// <summary>
        /// Název spřízněného editačního stylu
        /// </summary>
        public string EditStyleName { get { return _EditStyleName; } }
    }
    /// <summary>
    /// Atribut, který dovoluje (pro danou hodnotu enumu) určit zobrazovaný text.
    /// </summary>
    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
    public sealed class EditStyleDisplayValueAttribute : Attribute
    {
        /// <summary>
        /// Určí zobrazovaný text
        /// </summary>
        /// <param name="editStyleName">Zobrazovaný text</param>
        public EditStyleDisplayValueAttribute(string displayText)
        {
            this._DisplayText = displayText;
        }
        readonly string _DisplayText;
        /// <summary>
        /// Zobrazovaný text
        /// </summary>
        public string DisplayText { get { return _DisplayText; } }
    }
    #endregion
}
